home *** CD-ROM | disk | FTP | other *** search
/ Mac-Source 1994 July / Mac-Source_July_1994.iso / C and C++ / Utilities / Winter Shell 1.0d2 / Source / Special / ConcatenateTextFolders / ConcatenateTextFolders.c next >
Encoding:
C/C++ Source or Header  |  1994-01-19  |  11.6 KB  |  365 lines  |  [TEXT/KAHL]

  1. /*
  2.     Functions for concatenating and merging a list of folders containing
  3.     text files.
  4.     
  5.     Using this program you can merge text files from different folders.
  6.     Files with the same name that occur in more than one folder are
  7.     concatenated together in the order of the time of last modification, and
  8.     the resulting file is then placed in the destination folder. Files that
  9.     occur in only one source folder are essentially copied to the destination
  10.     folder. The original files are not modified.
  11.     
  12.     I wrote this to help me maintain my archives of newsgroups and mail
  13.     from my Unix account. For instance, say I read four newsgroups:
  14.     
  15.         comp.sys.mac.programmer
  16.         rec.backcountry
  17.         rec.bicycles
  18.         sci.med.occupational
  19.         
  20.     I keep archives of interesting articles from each newsgroup. Eventually,
  21.     there's so much on my Unix account, which has a limit of 5Mb, that
  22.     I need to download the archives. The first time this is easy: simply
  23.     download the files and put them in a folder called "News", then delete
  24.     them from the Unix account. However, now I continue reading news
  25.     and archive new messages. Eventually, my account fills up, and I have
  26.     to download the files once again. Now I have the old archives in
  27.     a folder called "News Old", and the new archives are in a folder
  28.     called "News New". Clearly, this process will repeat itself and
  29.     I will end up with many "News XXX" folders. This would make reading
  30.     archived articles very tedious.
  31.     
  32.     Here's an example of how this program might operate on three
  33.     folders named "News Oldest", "News Newer", and "News Newest";
  34.     the contents of each folder are shown in the following table.
  35.     
  36.     News Old                        News New                        News Newest
  37.     comp.sys.mac.programmer    comp.sys.mac.programmer comp.sys.mac.programmer
  38.     rec.backcountry            rec.backcountry            
  39.     sci.med.occupational                                        sci.med.occupational
  40.                                     rec.bicycles                
  41.                                     
  42.     After running this program on the three folders, the contents of
  43.     the output folder "News Out" will be:
  44.     
  45.         comp.sys.mac.programmer
  46.         rec.backcountry
  47.         sci.med.occupational
  48.         rec.bicycles
  49.     
  50.     The file "News Out:comp.sys.mac.programmer:" will consist of the
  51.     result of concatenating the file comp.sys.mac.programmer in each
  52.     of the three source folders. The first file to be concatenated will be
  53.     the file with the earliest modification time, the second file
  54.     to be concatenated will be the file with the next later modification
  55.     time, etc.
  56.     
  57.     The file "News Out:rec.backcountry:" will consist of the result of
  58.     concatenating the files "News Old:rec.backcountry:" and
  59.     "News New:rec.backcountry:". The file "News Out:sci.med.occupational:"
  60.     will consist of the result of concatenating the files
  61.     "News Old:sci.med.occupational:" and "News Newest:sci.med.occupational:".
  62.     Finally, the file "News Out:rec.bicycles:" will be the same as the
  63.     file "News New:rec.bicycles".
  64.     
  65.     After running this program, I will have a single folder containing all
  66.     of my archived messages. I can then read them using a program such
  67.     as Easy View or Eudora. Once I'm satisfied that the files were
  68.     downloaded and merged correctly I can delete the source folders
  69.     and remove the files from my Unix account.
  70.     
  71.     94/01/19 aih - added description
  72.     93/10/17 aih - continuing improvements
  73.     93/10/14 aih - created
  74. */
  75.  
  76. #include <stdio.h>
  77. #include <stdlib.h>
  78. #include <string.h>
  79. #include "pstr.h"
  80. #include "ConcatenateTextFolders.h"
  81. #include "DialogLib.h"
  82. #include "DialogModalLib.h"
  83. #include "FileDialogLib.h"
  84. #include "FolderLib.h"
  85. #include "LowMemLib.h"
  86. #include "MemoryLib.h"
  87. #include "ProgressLib.h"
  88. #include "ResourceConstantsLib.h"
  89. #include "ResourceLib.h"
  90. #include "SortLib.h"
  91. #include "TimeLib.h"
  92.  
  93. /* concatenate the contents of the source file's data fork to the
  94.     data fork of the destination file */
  95. static void FileConcatenate(FileType *src, FileType *dst)
  96. {
  97.     FilePosType    count;
  98.     char buf[FILE_BUFSIZ];
  99.  
  100.     TRY {
  101.         FileOpen(src, fsRdPerm);
  102.         FileOpen(dst, fsWrPerm);
  103.         FileSeek(dst, fsFromLEOF, 0);
  104.         FileCopyFork(src, dst);
  105.     } CLEANUP {
  106.         FileClose(src);
  107.         FileClose(dst);
  108.     } ENDTRY;
  109. }
  110.  
  111. /*---------------------------------------------------------------------------*/
  112.  
  113. /* get modification date of file */
  114. static TimeType FileModDate(FileType *fp)
  115. {
  116.     CInfoPBRec cat;
  117.  
  118.     FileCatalog(fp, &cat);
  119.     return(cat.hFileInfo.ioFlMdDat);
  120. }
  121.  
  122. /*---------------------------------------------------------------------------*/
  123.  
  124. /* structure used to sort files by modification date */
  125. typedef struct {
  126.     TimeType mod;
  127.     int index;
  128. } DateIndexType;
  129.  
  130. /* data used by action function */
  131. typedef struct {
  132.     DateIndexType *dates;    /* modification dates of source files */
  133.     FileType *src;                /* array of folders containing original files */
  134.     FileType *dst;                /* destination folder for merged files */
  135.     int nfolders;                /* number of folders to merge */
  136.     int index;                    /* index into array of folders */
  137.     ProgressHandle progress; /* progress dialog */
  138. } ConcatenateData;
  139.  
  140. /* function used to compare modification dates of two files during a sort */
  141. static int CompareFileDates(const void *a, const void *b)
  142. {
  143.     const DateIndexType *x = a, *y = b;
  144.     if (x->mod < y->mod) return(-1);
  145.     if (x->mod > y->mod) return(1);
  146.     return(0);
  147. }
  148.  
  149. /* This function is called by FileScan for every file and folder in the source
  150.     directories. The files in the source directories are concatenated and
  151.     placed in the destination directory. */
  152. static Boolean ConcatenateFiles(const CInfoPBRec *pb, void *data)
  153. {
  154.     ConcatenateData *catdata = data;
  155.     FileNameType name;                /* current file name */
  156.     Boolean dstexists = false;    /* true if destination file exists */
  157.     FileType *src = NULL;        /* current source file */
  158.     CStr31 prompt;                    /* prompt for progress dialog */
  159.     int i = 0;
  160.  
  161.     /* get name of current file */
  162.     p2cstrcpy(name, pb->hFileInfo.ioNamePtr);
  163.  
  164.     /* if the file already exists in the destination folder then it means
  165.         we've already concatenated it while scanning a prior source folder,
  166.         so it can be skipped this time through */
  167.     FileNameSet(catdata->dst, name);
  168.     dstexists = FileExists(catdata->dst);
  169.     if (! dstexists) {
  170.         
  171.         /* get the modification dates of the files with the current file's name in
  172.             each of the source folders, then sort the files by their modification
  173.             dates */
  174.         for (i = catdata->index; i < catdata->nfolders; i++) {
  175.             catdata->dates[i].mod = 0;
  176.             catdata->dates[i].index = i;
  177.             src = &catdata->src[i];
  178.             FileNameSet(src, name);
  179.             if (FileExists(src) && FileTypeGet(src) == 'TEXT')
  180.                 catdata->dates[i].mod = FileModDate(src);
  181.         }
  182.         qksort(&catdata->dates[catdata->index], catdata->nfolders - catdata->index,
  183.                 sizeof(DateIndexType), CompareFileDates);
  184.         
  185.         /* concatenate text files, starting with the oldest file */
  186.         for (i = catdata->index; i < catdata->nfolders; i++) {
  187.             if (catdata->dates[i].mod) { /* if non-zero then source file exists */
  188.                 ResStrLen(RLS_BUSY, RLS_BUSY_CONCATENATING, prompt, sizeof(prompt));
  189.                 ProgressPrompt(catdata->progress, prompt, name);
  190.                 src = &catdata->src[catdata->dates[i].index];
  191.                 if (dstexists)
  192.                     FileConcatenate(src, catdata->dst);
  193.                 else {
  194.                     FileCopy(src, catdata->dst);
  195.                     dstexists = true;
  196.                 }
  197.             }
  198.         }
  199.     }
  200.     
  201.     /* update progress dialog */
  202.     ProgressRun(catdata->progress, -1);
  203.     return(false);
  204. }
  205.  
  206. static void DoConcatenateTextFolders(ConcatenateData *catdata)
  207. {
  208.     long nfiles = 0;    /* number of files to process */
  209.     CStr31 prompt;        /* prompt for progress dialog */
  210.     int i = 0;
  211.     
  212.     TRY {
  213.     
  214.         /* setup progress dialog */
  215.         catdata->progress = ProgressBegin(nfiles, NULL, NULL, 0, 0);
  216.  
  217.         /* count number of files to process */
  218.         ResStrLen(RLS_BUSY, RLS_BUSY_COUNTING_FILES, prompt, sizeof(prompt));
  219.         ProgressPrompt(catdata->progress, prompt);
  220.         nfiles = 0;
  221.         for (i = 0; i < catdata->nfolders; i++) {
  222.             nfiles += FolderValence(&catdata->src[i], false);
  223.             ProgressRun(catdata->progress, 0);
  224.         }
  225.         ProgressReset(catdata->progress, nfiles);
  226.         
  227.         /* process each folder */
  228.         for (catdata->index = 0; catdata->index < catdata->nfolders; catdata->index++) {
  229.             
  230.             /* display folder name in progress dialog */
  231.             CInfoPBRec cat;
  232.             Str255 name;
  233.             memset(&cat, 0, sizeof(CInfoPBRec));
  234.             cat.dirInfo.ioVRefNum = catdata->src[catdata->index].vol;
  235.             cat.dirInfo.ioDrDirID = catdata->src[catdata->index].dir;
  236.             cat.dirInfo.ioNamePtr = name;
  237.             cat.dirInfo.ioFDirIndex = -1;
  238.             FailOSErr(PBGetCatInfo(&cat, false));
  239.             ResStrLen(RLS_BUSY, RLS_BUSY_SCANNING, prompt, sizeof(prompt));
  240.             ProgressPrompt(catdata->progress, prompt, p2cstr(name));
  241.             
  242.             /* scan the folder and concatenate files */
  243.             FileNameSet(&catdata->src[catdata->index], "");
  244.             FolderScan(&catdata->src[catdata->index], false,
  245.                             ConcatenateFiles, catdata);
  246.         }
  247.  
  248.     } CLEANUP {
  249.         ProgressEnd(catdata->progress);
  250.         catdata->progress = NULL;
  251.     } ENDTRY;
  252. }
  253.  
  254. /* filter out non-folders from custom file selection dialog */
  255. static pascal Boolean FolderFilter(ParamBlockRec *pb, void *data)
  256. {
  257.     return((pb->fileParam.ioFlAttrib & (1 << kFolderBit)) == 0);
  258. }
  259.  
  260. /* Merge the text files contained in the folders listed in 
  261.     'src' into the folder 'dst'. If 'src' is NULL then the user
  262.     is prompted to select a list of folders. If 'dst' is NULL then
  263.     the user is prompted to select a destination folder. */
  264. void ConcatenateTextFolders(FileType *src, int nfolders, FileType *dst)
  265. {
  266.     ProgressHandle progress = NULL;/* progress info */
  267.     FileHandle srcfolders = NULL;    /* handle to source folders */
  268.     FileType dstfolder;                /* destination folder */
  269.     ConcatenateData catdata;        /* information needed while merging folders */
  270.     FileNameType name;                /* default name of destination folder */
  271.     CInfoPBRec cat;                    /* for setting folder's modification date */
  272.     FileType svdst;                    /* destination folder, for setting mod date */
  273.     CStr31 prompt;                        /* prompt for dialogs */
  274.     int i = 0;
  275.  
  276.     TRY {
  277.         
  278.         /* select source folders */
  279.         if (! src) {
  280.             srcfolders = FileSelectMultipleCustom(FolderFilter,
  281.                 NULL, NULL, NULL, NULL, NULL);
  282.             nfolders = HandleSize(srcfolders) / sizeof(FileType);
  283.             (void) HandleLockHi(srcfolders);
  284.             src = *srcfolders;
  285.         }
  286.         if (nfolders > 0) {
  287.         
  288.             /* get directory ID and strip file names */
  289.             for (i = 0; i < nfolders; i++)
  290.                 FileDirID(&src[i]);
  291.  
  292.             /* select destination folder */
  293.             if (! dst) {
  294.                 dst = &dstfolder;
  295.                 ResStrLen(RLS_SF, RLS_SF_FOLDER, prompt, sizeof(prompt));
  296.                 ResStrLen(RLS_FILE, RLS_FILE_UNTITLED_FOLDER, name, sizeof(name));
  297.                 FileSet(dst, -GetSFSaveDisk(), GetCurDirStore(), name);
  298.                 FileUnique(dst);
  299.                 strcpy(name, FileName(dst));
  300.                 FileSFPut(dst, prompt, name);
  301.             }
  302.             
  303.             /* setup file merge data */
  304.             memset(&catdata, 0, sizeof(ConcatenateData));
  305.             catdata.dates = PtrBeginClear(sizeof(DateIndexType) * nfolders);
  306.             catdata.nfolders = nfolders;
  307.             catdata.dst = dst;
  308.             catdata.src = src;
  309.  
  310.             /* create destination folder */
  311.             check(! FolderExists(dst));
  312.             FileClone(dst, &svdst);
  313.             if (FileExists(dst))
  314.                 FileDelete(dst);
  315.             FolderCreate(dst);
  316.             FileDirID(dst);
  317.             FileVerifySet(dst, true);
  318.     
  319.             /* merge folders */
  320.             DoConcatenateTextFolders(&catdata);
  321.             
  322.             /* set modification time so finder will update its windows */
  323.             FileCatalog(&svdst, &cat);
  324.             GetDateTime(&cat.dirInfo.ioDrMdDat);
  325.             FileCatalogSet(&svdst, &cat);
  326.         }
  327.     } CLEANUP {
  328.         PtrEnd(catdata.dates);
  329.         HandleEnd(srcfolders);
  330.     } ENDTRY;
  331. }
  332.  
  333. #ifdef NOT_USED
  334.  
  335.     /* could be used to support a drag-and-drop utility */
  336.  
  337. /* concatenate the list of folders */
  338. static void HLEOpen(AEDescList *list, long n, void *data)
  339. {
  340.     FileType file, *fp = &file;
  341.     FileType **files = NULL;
  342.     long nfiles = 0;
  343.     long i = 0;
  344.     
  345.     TRY {
  346.         for (i = 1; i <= n; i++) {
  347.             AEGetNthFile(list, i, fp);
  348.             if (FolderExists(fp)) { /* process only folders */
  349.                 if (! files)
  350.                     files = HandleBegin(0);
  351.                 HandleSizeSet(files, sizeof(FileType) * (nfiles + 1));
  352.                 (*files)[nfiles++] = *fp;
  353.             }
  354.         }
  355.         if (files) {
  356.             HandleLockHi(files);
  357.             ConcatenateTextFolders(*files, nfiles, NULL);
  358.         }
  359.     } CLEANUP {
  360.         HandleEnd(files);
  361.     } ENDTRY;
  362. }
  363.  
  364. #endif /* NOT_USED */
  365.